asynx.namespace("ajax");

asynx.ajax = function(attr) {
	this.init(attr);
};

asynx.ajax.SYNC_AJAX_SUBMIT = false;

asynx.ajax.prototype = {
	
	init: function(attr) {
		this.url = attr.url;
		this.method = attr.method?attr.method:'post';
		this.format = attr.format?attr.format:'text';
		this.charset = attr.charset?attr.charset:'UTF-8';
		this.ifModified = attr.ifModified===true?true:false;
		this.ifNotModified = attr.ifNotModified;
		this.ifNotModifiedArgs = attr.ifNotModifiedArgs;
		this.onComplete = attr.onComplete;
		this.onCompleteArgs = attr.onCompleteArgs;
		this.loading = attr.loading;
		this.loadingArgs = attr.loadingArgs;
		this.onError = attr.onError;
		this.onErrorArgs = attr.onErrorArgs;
		this.json = attr.json===true?true:false;
		this.async = attr.async===false?false:true;
		this.script = attr.script===true?true:false;
		this.requestValues = {};
		this.requestObjects = [];
		this.extraData = null;
		this.xmlhttp = asynx.ajax.thread.getXMLHttpRequest();
		this.ajaxSubmit = false;
		this.linkedForm = null;
		this.resetForm = false;
	},
	
	load: function() {
		asynx.ajax.thread.run(this);				
	},
	
	addValue: function(sKey, sValue) {
		this.requestValues[sKey] = sValue;
	},
	
	addObject: function(el) {
		var element = asynx$(el);
		try{
			this.requestObjects.push({name: element.name, value: element.value});
		}
		catch(e){ /*TODO*/ };
	},
	
	partialSubmit: function(form) {
		
		var oform = asynx(form), fields = arguments[1];
		
		this.extraData = asynx.serializeForm(oform, fields);
		
	},
	
	submit: function(form) {
		var args = arguments;
		
		this.ajaxSubmit = true;
		
		this.resetForm = args[1];
		
		this.linkedForm = form;
	},
	
	buildRequestString: function() {
		var string = new asynx.StringBuffer();
		for (key in this.requestValues) {
			if (typeof this.requestValues[key] !== 'function') {
				var value = this.requestValues[key];
				string.append([key, '=', encodeURIComponent(value), "&"].join(""));
			};
		};
		for (x in this.requestObjects) {
			var object = this.requestObjects[x];
			if (object.name) { string.append([object.name, '=',encodeURIComponent(object.value), "&"].join("")); };
		};
		
		var data = new asynx.StringBuffer();
		data.append(string.toString());
		data.append(this.extraData || "");
		
		return data.toString();
	}
	
};

asynx.ajax.thread = {
	
	states: ['uninitialized', 'open', 'sent', 'receiving', 'loaded'],
	locked: false,
	thread: 0,
	
	run: function(connector) {
		
		asynx.globals.ajax.threads.push(connector);
		
		if (!this.locked)
			this.dispatch();
			
	},
	
	dispatch: function() {
		
		this.locked = true;
		var connector = asynx.globals.ajax.threads[asynx.ajax.thread.thread];
		
		
		if (!connector) {
			this.locked = false;
			return;
		};
		
		if (connector.ajaxSubmit) {
			
			if (!asynx.ajax.SYNC_AJAX_SUBMIT)
				asynx.ajax.thread.thread++;
				
			this.ajaxSubmit(connector);	
			return;	
		};
		
		try {

			var xmlhttp = connector.xmlhttp;
			var sinceModified = asynx.globals.ajax.urlModifiedDates[connector.url] || new Date(0);
			
			var headers = [
				["If-Modified-Since", sinceModified],
				["X-Requested-With", 'XMLHttpRequest'],
				["Content-Type", 'application/x-www-form-urlencoded; charset='+connector.charset],
				["Content-Length", connector.buildRequestString().length]
			];
			
			this.callLoading(connector);
			
			var isGet = !!(connector.method.toLowerCase() == 'get');
			var url = isGet ? connector.url+"?"+connector.buildRequestString() : connector.url;
			var sendData = isGet?null:connector.buildRequestString();
			
			xmlhttp.open(connector.method, url, connector.async);
			
			if (!asynx.isIE && connector.format == "xml")
				xmlhttp.overrideMimeType('text/xml');
			
			/*
			 * headers
			 */
			asynx.each(headers, function(header) {
				xmlhttp.setRequestHeader(header[0], header[1]);
			});
			
			xmlhttp.send(sendData);
			
			this.callLoading(connector);
			
			xmlhttp.onreadystatechange = function() {
				asynx.ajax.thread.processReadyState(connector);
			};
			
		}catch(e) {
			asynx.ajax.thread.finalize(connector);
		};
		
	},
	
	processReadyState: function(connector) {
		try {
			
			var xml = connector.xmlhttp;
			this.callLoading(connector);
			
			if (xml.readyState == 4) {
				
				var status = xml.status;
				var translated = this.translate(status);
				
	            if (translated == 'OK') {
					
					try {
						
						/*var type = xml.getResponseHeader("Content-Type");*/
						
						var urlModDates = asynx.globals.ajax.urlModifiedDates;
						var lastMod = xml.getResponseHeader("Last-Modified");
						
						// cache url x lastDate
						if (connector.ifModified && lastMod)
							urlModDates[connector.url] = lastMod;
							
					}
					catch(e){};
	
					this.callComplete(connector);
					this.done();
	            }
				
				else if (translated == 'Not-Modified') {
					this.callNotModified(connector);
					this.done();
				}
				
				else {
					this.callError(connector);
					this.done();
	            }
				
	        }
		
		}
		catch(e){
			asynx.ajax.thread.finalize(connector);
		}
	},
	
	translate: function(status) {
		var index = {
			200:"OK", 304:"Not-Modified",
			400:"Bad Request", 401:"Unauthorized",
			403:"Forbidden", 404:"Not-Found",
			500:"Internal Server Error"
		};
		return index[status];
	},
	
	ajaxSubmit: function(connector) {
		var uid = "asynx_ajax_submit_" + asynx.uid();
		var iframe = asynx.createIframe(uid);
		var lForm = connector.linkedForm;
		var originalForm = asynx.extend({}, lForm);
		var extra = connector.buildRequestString();
		var scope = this;
		
		var emulateLoading = function(status) {
			if (connector.loading)
				connector.loading.apply(this, [status, connector.loadingArgs]);
		}
		
		emulateLoading("loading");
		
		asynx.each(extra.split("&"), function(part) {
			if (part && part.constructor == String) {
				
				var key = part.split("=")[0], value = part.split("=")[1];
				
				if (key && value && !lForm.elements[key]) {
					var hidden = document.createElement("input");
					hidden.type = 'hidden';
					hidden.name = key;
					hidden.value = value;
					hidden.setAttribute("asynxId", "__asynx_hidden_"+asynx.uid());
					lForm.appendChild(hidden);			
				}
			}
		});
		
		lForm.enctype = 'multipart/form-data';
		lForm.encoding = 'multipart/form-data';
		lForm.target = uid;
		lForm.method = connector.method;
		lForm.action = connector.url||lForm.action;
		
		if (!lForm.action) {
			// abort if url isempty
			asynx.ajax.thread.done();
			return;
		}
		
		lForm.submit();
		
		// reset form
		lForm.target = originalForm.action;
		lForm.action = originalForm.target;
		
		asynx("//input[@asynxId ^= '__asynx_hidden']", lForm).each(function(hidden) {
			if (!asynx.isArray(hidden))
				lForm.removeChild(hidden);
		});
		
		if (connector.resetForm)
			lForm.reset();
		
		// callback
		var fn = function() {
			
			emulateLoading("complete");
			
			var r = asynx.iframeDoc(iframe).body.innerHTML;
			
			connector.onComplete.apply(this, [r, connector.onCompleteArgs]);
			
			asynx.delay(function() {
				
				asynx.dom.remove(asynx("/body"), iframe);
				
			}, 100);
			
			if (asynx.ajax.SYNC_AJAX_SUBMIT)
				asynx.ajax.thread.done();
				
		};
		
		// mozilla and opera
		iframe.onload = function() {
			fn.apply(this);
		};
		 // ie
		if (asynx.isIE) {
			iframe.onreadystatechange = function() {
				var readyState = iframe.readyState;
				if (readyState == "complete") fn.apply(this);
			};
		};
		
		if (!asynx.ajax.SYNC_AJAX_SUBMIT)
			scope.locked = false;
	},
	
	next: function() {
		asynx.ajax.thread.thread++;
		this.locked = false;
		this.dispatch();
	},
	
	done: function() {
		asynx.ajax.thread.next();
	},
	
	callLoading: function(connector) {
		if (connector.loading)
			connector.loading.apply(this, 
				[asynx.ajax.thread.states[connector.xmlhttp.readyState], connector.loadingArgs]);
	},
	
	callComplete: function(connector) {
		
		if (!connector.onComplete)
			return false;
		
		var xml = connector.xmlhttp;
		
		var isTextData = !!(connector.format == 'text');
		var response = isTextData?xml.responseText:xml.responseXML;
		
		
		if (isTextData && connector.script) {
			try { eval(response); }
			catch(e){};
		};
			
		if (isTextData && connector.json && !connector.script)
			response = asynx.evalJSON(xml.responseText);
		
		connector.onComplete.apply(this, [response, connector.onCompleteArgs]);
	},
	
	callError: function(connector) {
		var status = connector.xmlhttp.status;
		var translated = this.translate(status);
		if (connector.onError)
			connector.onError.apply(this, 
				[translated?translated:status, connector.xmlhttp.responseText, connector.onErrorArgs]);
	},
	
	callNotModified: function(connector) {
		if (!connector.ifNotModified) return false;
		connector.ifNotModified.apply(this, [connector.ifNotModifiedArgs]);
	},
	
	getXMLHttpRequest: function() {
		if (window.ActiveXObject) 
			return new ActiveXObject("Microsoft.XMLHTTP");
		else
			return new XMLHttpRequest();
	},
	
	finalize: function(connector) {
		try { connector.xmltttp.abort(); }
		catch(e) { /* TODO	*/ }
		asynx.ajax.thread.next();
	}
	
};

asynx.ajax.impl = {
	
	$open: function(url) {
		var data = arguments[1], fn = arguments[2], 
			fnargs = arguments[3], method = arguments[4],
			json = arguments[5], script = arguments[6], f = arguments[7];
		
		var ajax = new asynx.ajax({
			url:url,
			method:method?method:'post',
			format:f?f:'text',
			json:json==true?true:false,			
			script:script==true?true:false,			
			onComplete: function(r, args) {
				if (fn)	fn.apply(this, [script?args:r, args]);
			},
			onCompleteArgs: fnargs
		});
		
		asynx.forEach(data, function(k, v) {
			ajax.addValue(k,v);
		});
		ajax.load();
		
		return ajax;
	},
	
	$get: function(url) {
		var data = arguments[1];
		var fn = arguments[2];
		var fnargs = arguments[3];
		return this.$open(url, data, fn, fnargs, "get");
	},
	
	$post: function(url) {
		var data = arguments[1];
		var fn = arguments[2];
		var fnargs = arguments[3];
		return this.$open(url, data, fn, fnargs, "post");
	},
	
	$submit: function(el, url) {
		el = asynx(el);
		var data = arguments[2];
		var fn = arguments[3];
		var fnargs = arguments[4];
		var method = arguments[5];
		
		var ajax = new asynx.ajax({
			url: url,
			method: method?method:'post',
			onComplete: function(r, args) {
				if (fn)	fn.apply(this, [r, args]);
			},
			onCompleteArgs: fnargs
		});
		
		asynx.forEach(data, function(k, v) {
			ajax.addValue(k,v);
		});
		
		ajax.submit(el, false);
		ajax.load();
		
		return ajax;
	},
	
	$getJSON: function(url) {
		var data = arguments[1];
		var fn = arguments[2];
		var fnargs = arguments[3];
		return this.$open(url, data, fn, fnargs, "post", true);
	},
	
	$getXML: function(url) {
		var data = arguments[1];
		var fn = arguments[2];
		var fnargs = arguments[3];
		return this.$open(url, data, fn, fnargs, "post", false, false, 'xml');
	},
	
	$getScript: function(url) {
		var data = arguments[1];
		var fn = arguments[2];
		var fnargs = arguments[3];
		return this.$open(url, data, fn, fnargs, "post", false, true);
	},
	
	$ifModified: function(url) {
		var data = arguments[3], fn = arguments[1],
			fnargs = arguments[4], fMod = arguments[2],
			fnModargs = arguments[5], f = arguments[6];
		
		var ajax = new asynx.ajax({
			ifModified:true,
			url:url,
			method:'get',
			format:f?f:'text',
			onComplete: function(r, args) {
				if (fn)	fn.apply(this, [r, args]);
			},
			onCompleteArgs: fnargs,
			
			ifNotModified: function(args) {
				if (fMod)	fMod.apply(this, [args]);
			},
			ifNotModifiedArgs: fnModargs
		});
		
		asynx.forEach(data, function(k, v) {
			ajax.addValue(k,v);
		});
		ajax.load();
	}
	
};

asynx.extend(asynx, asynx.ajax.impl);
delete asynx.ajax.impl;
